package com.chuangjiangx.member.stored.web.controller;

import com.chuangjiangx.commons.CurrentThreadContext;
import com.chuangjiangx.commons.exception.BaseException;
import com.chuangjiangx.commons.redis.RedisKeyManager;
import com.chuangjiangx.commons.response.CamelResponse;
import com.chuangjiangx.commons.response.Response;
import com.chuangjiangx.commons.response.ResponseUtils;
import com.chuangjiangx.commons.wx.auth.WXWebAuthType;
import com.chuangjiangx.domain.shared.model.PayEntry;
import com.chuangjiangx.member.basic.ddd.application.EntryApplication;
import com.chuangjiangx.member.basic.web.response.MemberCodeReponse;
import com.chuangjiangx.member.coupon.ddd.application.MbrCouponApplication;
import com.chuangjiangx.member.coupon.web.response.MbrHasCouponResponse;
import com.chuangjiangx.member.stored.ddd.application.dto.MbrPayOrderResult;
import com.chuangjiangx.member.coupon.dao.model.InMbrHasCoupon;
import com.chuangjiangx.member.coupon.ddd.domain.model.MbrCoupon;
import com.chuangjiangx.member.coupon.ddd.domain.repository.MbrCouponRepository;
import com.chuangjiangx.member.coupon.ddd.domain.model.MbrHasCoupon;
import com.chuangjiangx.member.coupon.ddd.domain.repository.MbrHasCouponRepository;
import com.chuangjiangx.member.basic.ddd.domain.model.MbrUserMapping;
import com.chuangjiangx.member.basic.ddd.domain.model.MbrUserMappingRepository;
import com.chuangjiangx.member.score.ddd.domain.model.MbrScoreGrandTotalRule;
import com.chuangjiangx.member.score.ddd.domain.repository.MbrScoreGrandTotalRuleRepository;
import com.chuangjiangx.member.basic.web.controller.BaseController;
import com.chuangjiangx.member.basic.web.interceptor.Login;
import com.chuangjiangx.member.stored.web.request.CtobPayRequest;
import com.chuangjiangx.member.stored.service.H5PayService;
import com.chuangjiangx.member.stored.service.command.H5PayCommand;
import com.chuangjiangx.member.stored.service.model.WxMicroPayResult;
import com.chuangjiangx.member.common.utils.CodeUtils;
import com.chuangjiangx.member.basic.ddd.query.MbrAccountQuery;
import com.chuangjiangx.member.coupon.ddd.query.MbrCouponQuery;
import com.chuangjiangx.member.basic.ddd.query.MemberQuery;
import com.chuangjiangx.member.basic.ddd.query.condition.MbrUserMapingCondition;
import com.chuangjiangx.member.coupon.ddd.dal.dto.MbrHasCouponList;
import com.chuangjiangx.member.basic.ddd.dal.dto.MbrAccountDTO;
import com.chuangjiangx.member.basic.ddd.query.dto.MemberDTO;
import com.chuangjiangx.member.basic.ddd.query.dto.MemberDetailDTO;
import com.chuangjiangx.member.coupon.ddd.domain.model.MbrHasCouponId;
import com.chuangjiangx.member.basic.ddd.domain.model.MbrUserContext;
import com.chuangjiangx.member.basic.ddd.domain.model.MbrUserMappingType;
import com.chuangjiangx.member.basic.ddd.domain.model.MemberId;
import com.chuangjiangx.member.basic.ddd.domain.model.MerchantId;
import com.chuangjiangx.member.stored.ddd.domain.exception.MemberPaymentException;
import com.chuangjiangx.member.common.utils.RequestUtils;
import com.chuangjiangx.member.stored.web.response.CtobPayResponse;
import com.chuangjiangx.member.stored.web.response.PayMbrInfoResponse;
import com.chuangjiangx.member.stored.web.response.PaymentResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * @author chenhao
 */
@Api(tags = "支付",description = "支付相关接口")
@Slf4j
@RestController
@RequestMapping(value = "/h5/pay", produces = "application/json")
public class PayController extends BaseController {

    @Value("${member.h5.domain}")
    private String h5Domain;

    @Autowired
    private MemberQuery memberQuery;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private MbrAccountQuery mbrAccountQuery;
    @Autowired
    private MbrCouponQuery mbrCouponQuery;
    @Autowired
    private MbrCouponApplication mbrCouponApplication;
    @Autowired
    private MbrHasCouponRepository mbrHasCouponRepository;
    @Autowired
    private MbrCouponRepository mbrCouponRepository;
    @Autowired
    private MbrUserMappingRepository mbrUserMappingRepository;
    @Autowired
    private H5PayService h5PayService;
    @Autowired
    private MbrScoreGrandTotalRuleRepository mbrScoreGrandTotalRuleRepository;
    @Autowired
    private EntryApplication entryApplication;


    /**
     * 生成会员的 条形码 二维码
     */
    @RequestMapping(value = "/create-pay-qrcode", produces = "application/json")
    @Login
    public Response createPayMemberCode() {
        /**
         * 查询会员的信息,包括会员卡号\会员卡当前余额\会员卡当前可用积分等
         */
        MbrUserContext userContext = (MbrUserContext) CurrentThreadContext.getCurrentUser();
        Long memberId = userContext.getMemberId();
        MemberDetailDTO memberDetailDTO = memberQuery.searchMemberDetailById(memberId);
        if (null == memberDetailDTO) {
            throw new BaseException("", "会员不存在!");
        }

        MemberCodeReponse reponse = new MemberCodeReponse();

        String code = RedisKeyManager.MEMBER_PAY_CODE_PREFIX + RandomStringUtils.randomNumeric(18);

        redisTemplate.opsForValue().set(code, String.valueOf(memberId), 2, TimeUnit.MINUTES);

        //生成条形码
        String barcode = CodeUtils.generateBarcode(code);
        //转成base64
        reponse.setBarCode(barcode);

        //生成二维码
        String qrconde = CodeUtils.generateQrCode(code);
        reponse.setQrCode(qrconde);

        //当前余额
        reponse.setAvailableBalance(memberDetailDTO.getAvailableBalance());
        //会员卡号
        reponse.setMemberCardNum(memberDetailDTO.getMemberCardNum());
        reponse.setPayCode(code);
        reponse.setDomain(h5Domain);
        return ResponseUtils.success(reponse);
    }


    /**
     * 根据uid查询是否会员,如果是会员,则查询出会员的余额和领取的卡券列表
     * @param request
     * @param openid
     */
    @ApiOperation(value = "支付时查询会员信息",notes = "信息包括:是否会员,会员积分,会员储值余额,会员领取卡券列表")
    @RequestMapping(value = "/query-mbr-info",method = RequestMethod.GET)
    public CamelResponse<PayMbrInfoResponse> queryMbrInfo(
            HttpServletRequest request,
            @RequestParam Long merchantId,
            @RequestParam String openid
    ){
        PayMbrInfoResponse response = new PayMbrInfoResponse();
        // 获取当前用户判断是否已经登录
        response.setLogin(isValidLogin());
        PayEntry payEntry = RequestUtils.getPayEntry(request);
        // 默认微信
        payEntry = (null == payEntry ? PayEntry.WXPAY : payEntry);
//        if(null == payEntry){
//            throw new MemberPaymentException("","暂不支持:"+payEntry);
//        }
        MemberDTO memberDTO = getMemberInfoWithOpenid(merchantId, openid, payEntry);
        if(null == memberDTO){
            //非会员
        }else{
            MbrAccountDTO account = mbrAccountQuery.queryBymemberId(memberDTO.getId());
            List<MbrHasCouponList> mbrHasCouponList = mbrCouponQuery.findMbrHasCouponList(memberDTO.getId(), 0);
            //排序mbrHasCouponList,按卡券剩余使用时间及面额排序
            final long now = System.currentTimeMillis();
            List<MbrHasCouponResponse> list = mbrHasCouponList
                    .stream()
                    .sorted(Comparator.comparing(s -> {
                        if (0 == s.getValidType()) {
                            //固定时间
                            return s.getValidTimeEnd().getTime() - now - s.getAmount().intValue();
                        } else {
                            //领取后有效时间
                            return DateUtils.addDays(s.getClaimTime(), s.getClaimedTime()).getTime() - now - s.getAmount().intValue();
                        }
                    })).map(s -> {
                        MbrHasCouponResponse r = new MbrHasCouponResponse();
                        org.springframework.beans.BeanUtils.copyProperties(s, r);
                        return r;
                    }).collect(Collectors.toList());
//            List<MbrHasCouponResponse> list = BeanUtils.convertCollection(mbrHasCouponList, MbrHasCouponResponse.class);
            response.setMember(true);
            response.setMemberId(account.getMemberId());
            response.setAvailableBalance(account.getAvailableBalance());
            response.setAvailableScore(account.getAvailableScore());
            response.setMbrHasCouponList(list);
        }
        return (CamelResponse<PayMbrInfoResponse>)ResponseUtils.successCamel(response);
    }

    private MemberDTO getMemberInfoWithOpenid( Long merchantId,  String openid, PayEntry payEntry) {
        MbrUserMappingType mappingType = MbrUserMappingType.fromValue((byte) payEntry.value);
        if(mappingType == null){
            throw new MemberPaymentException("","未知用户类型");
        }
        MbrUserMapingCondition condition = new MbrUserMapingCondition();
        condition.setMerchantId(merchantId);
        condition.setType(mappingType.value);
        condition.setOpenId(openid);
        return memberQuery.queryByMbrUserMapingCondition(condition);
    }

    /**
     * H5 C扫B支付+卡券核销
     * 分4中逻辑
     * 1.使用卡券
     *      一.使用会员卡支付
     *      二.使用微信\支付宝支付...
     * 2.不使用卡券
     *      一.使用会员卡支付
     *      二.使用微信\支付宝支付...
     */
    @ApiOperation(value = "H5 C扫B支付+卡券核销")
    @RequestMapping(value = "/ctobPay",method = RequestMethod.POST)
    public CamelResponse<CtobPayResponse> ctobPay(
            HttpServletRequest httpServletRequest,
            @RequestBody @Validated CtobPayRequest request
            ){
        //用户选择的支付入口
        PayEntry payEntry = PayEntry.getPayEntry(request.getPayEntry());
        //标识是否使用卡券
        boolean useCoupon = false;
        MbrHasCoupon mbrHasCoupon = null;
        MbrCoupon mbrCoupon = null;
        //会员信息
        MemberDTO memberDTO = null;
        if(null != request.getMbrHasCouponId()){
            //使用了卡券下单流程
            mbrHasCoupon = mbrHasCouponRepository.fromId(new MbrHasCouponId(request.getMbrHasCouponId()));
            if(null == mbrHasCoupon){
                throw new MemberPaymentException("","卡券不存在");
            }
            mbrCoupon = mbrCouponRepository.fromId(mbrHasCoupon.getMbrCouponId());
            //计算卡券优惠金额
            BigDecimal discountAmount = mbrCoupon.calcDiscountAmount(request.getAmount());
            if(request.getAmount()
                    .subtract(discountAmount)
                    .compareTo(BigDecimal.ZERO) <= 0){
                //0元订单必须走会员卡支付入口
                if(!Objects.equals(PayEntry.MSCARDPAY,payEntry)){
                    throw new MemberPaymentException("","非会员卡支付方式不支持0元订单");
                }
                //0元订单一定是会员
            }
            //使用了卡券用户一定是正式会员
            useCoupon = true;
        }
        if(Objects.equals(PayEntry.MSCARDPAY,payEntry)){
            memberDTO = memberQuery.queryByMemberId(request.getMemberId());
        }else{
            memberDTO = getMemberInfoWithOpenid(request.getMerchantId(), request.getOpenid(), payEntry);
        }

        CtobPayResponse response = h5PayService.pay(new H5PayCommand(
                httpServletRequest,
                request,
                payEntry,
                useCoupon,
                mbrHasCoupon,
                memberDTO
        ));
        // 生成付款结果
        PaymentResult paymentResult = prePaymentResult(request, payEntry, mbrCoupon, memberDTO, response);
        response.setPaymentResult(paymentResult);
        CamelResponse<CtobPayResponse> result = new CamelResponse<>();
        result.setData(response);
        result.setSuccess(true);
        return result;
    }

    private PaymentResult prePaymentResult(CtobPayRequest request, PayEntry payEntry, MbrCoupon mbrCoupon, MemberDTO memberDTO, CtobPayResponse response) {
        PaymentResult pr = new PaymentResult();
        switch (payEntry){
            case MSCARDPAY:
                MbrPayOrderResult mr = response.getMbrPayOrderResult();
                pr.setAmount(mr.getTotalFee());
                pr.setDiscountAmount(mbrCoupon == null ? BigDecimal.ZERO : mbrCoupon.calcDiscountAmount(request.getAmount()));
                pr.setRealPayAmount(mr.getRealPayFee());
                pr.setPayEntry(PayEntry.MSCARDPAY.value);
                pr.setPayTime(mr.getPayTime());
                pr.setOrderNumber(mr.getOrderNumber());
                pr.setMobile(mr.getMobile());
                pr.setScore(mr.getScore());
                pr.setAvailableBalance(mr.getAvailableBalance());
                break;
            case ALIPAY:
//                AliMicroPayResult ar = response.getAliMicroPayResult();
//                pr.setAmount(request.getAmount());
//                pr.set
                break;
            case WXPAY:
                MbrScoreGrandTotalRule mbrScoreGrandTotalRule = mbrScoreGrandTotalRuleRepository.fromMerchantId(new MerchantId(request.getMerchantId()));
                WxMicroPayResult wr = response.getWxMicroPayResult();
                pr.setAmount(request.getAmount());
                pr.setDiscountAmount(mbrCoupon == null ? BigDecimal.ZERO : mbrCoupon.calcDiscountAmount(request.getAmount()));
                pr.setRealPayAmount(pr.getAmount().subtract(pr.getDiscountAmount()));
                pr.setPayEntry(PayEntry.WXPAY.value);
                pr.setPayTime(new Date());
                pr.setOrderNumber(wr.getOrderNumber());

                if(null != memberDTO){
                    pr.setMobile(memberDTO.getMobile());
                    pr.setScore(mbrScoreGrandTotalRule == null ? 0L : mbrScoreGrandTotalRule.calcObtainScore(pr.getRealPayAmount()));
                }
                break;
            default:
                break;
        }
        pr.setCouponName(mbrCoupon == null ? null : mbrCoupon.getName());
        return pr;
    }

    /**
     * 支付回调
     * @param request
     * @return
     */
    @ApiOperation(value = "支付回调接口",httpMethod = "GET")
    @RequestMapping(value = "/call-back")
    public ResponseEntity<String> callbackUrl(
            HttpServletRequest request) {
        //TODO... 支付成功回调处理
        log.info("支付回调=====");
        log.info("参数:{}",request.getParameterMap());
        try {
            log.info("post数据:{}",IOUtils.toString(request.getInputStream(),"utf-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ResponseEntity<>("SUCCESS",HttpStatus.OK);
    }

    /**
     * 微信卡包中点击去使用
     * 微信卡券支付中间跳转（获取openid）
     *
     * ###跳转H5C扫B:http://test.member.h5.chuangjiangx.com/app/index.html#/payment/ctob
     * ?merchantId=1308&openid=o6FD7spseZ7n0-pJYhbtJd94AR0M&amount=0.01&websocketId=&storeId=331&merchantUserId=1727&qrcodeId=628
     */
    @ApiOperation(value = "微信卡券支付中间跳转（获取openid）",httpMethod = "GET")
    @RequestMapping(value = "/redirect-wx-cash")
    public ModelAndView redirectWxCash(HttpSession session, String encrypt_code, String card_id, Long merchant_id) {
        log.info("微信卡券支付中间跳转=====");
        log.info("参数：encrypt_code={};card_id={};merchant_id={}",encrypt_code,card_id,merchant_id);
        if(merchant_id==null){
            throw new BaseException("","该卡券有误，请联系商家使用新卡券（merchant_id is null）");
        }
        //解码code,得出来的是卡券核销码
        String code = mbrCouponApplication.codeDecrypt(encrypt_code,new MerchantId(merchant_id));
        //根据code查询卡券,查询会员身份
        InMbrHasCoupon mbrHasCoupon = mbrCouponQuery.queryInMbrHasCoupon(code);
        if(mbrHasCoupon == null){
            throw new BaseException("","卡券核销码"+code+"不存在!");
        }
        MbrUserMapping mbrUserMapping = mbrUserMappingRepository.from(
                new MemberId(mbrHasCoupon.getMemberId()),
                new MerchantId(merchant_id),
                MbrUserMappingType.WX
        );
        // ? mbrUserMapping在这里不应该为null
        if(null != mbrUserMapping && StringUtils.isNotBlank(mbrUserMapping.getOpenId())){
            return new ModelAndView(new RedirectView(h5Domain+"/app/index.html#/payment/ctob?merchantId="+merchant_id+"&openid="+mbrUserMapping.getOpenId()+"&verifyCode="+code));
        }else{
            // 出现场景:用户在小程序注册(未在H5中注册),卡券同步到微信后,点击立即使用
            // 去授权获取用户openid(服务商公众号)
            session.setAttribute("needIsvOpenId","coupon-use-immediately");
            session.setAttribute("cardId",card_id);
            session.setAttribute("code",code);
            MemberDTO member = memberQuery.queryByMemberId(mbrHasCoupon.getMemberId());
            MbrUserContext userContext = loginService.currentUserContext();
            userContext.setMemberId(member.getId());
            userContext.setMerchantId(merchant_id);
            userContext.setMobile(member.getMobile());
            userContext.setHeadimgurl(member.getHeadimgurl());
            loginService.updateUserContext(userContext);
            String isvWxWebAuthUrl = entryApplication.isvWxWebAuthUrl(merchant_id, WXWebAuthType.BASE);
            return new ModelAndView("redirect:" + isvWxWebAuthUrl);
        }
    }

}
